#include "vcl_stl.h"

#include "bkbase.h"

#include "h_base.h"
#include "h_xml.h"
using namespace h_base;

#include "simbat.h"
using namespace simbat;

struct HJobInfo
{
   map<HString, HString> PropertyMap;
   map<HString, vector<HString> > MultiLinePropertyMap;
};

struct HGroupInfo
{
   map<HString, HJobInfo> JobMap;
   set<HString> LinkSet;
};

struct HSbtInfo
{
   map<HString, HString> PropertyMap;
   map<HString, vector<HString> > MultiLinePropertyMap;
   map<HString, HGroupInfo> GroupMap;
};

static char *SimbatBase;
static HSbtInfo OldSbt;
static HSbtInfo NewSbt;
static HString JobNameDelimiter = ":";

static char ErrMsg[8192 + 1];

static void LoadFromCfg(HXmlTree &Cfg)
{
   JobNameDelimiter = Cfg.GetValue("JOB_NAME_DELIMITER", ":");
}

static bool LoadCfg(HString &ErrStr)
{
   char FileName[500 + 1];
   HXmlTree Cfg;

   {
      HFileLockRead Lock("$SIMBAT_BASE/tmp/config.lck");
      snprintf(FileName, 500, "%s/etc/config.xml", SimbatBase);
      if ( !Cfg.LoadFromFile(FileName) )
      {
         ErrStr = Cfg.GetErrorString();
         return false;
      }
   }

   LoadFromCfg(Cfg);
   return true;
}

void LoadProperty(HXmlTree *Xml, const char *Tag, map<HString, HString> &PropertyMap)
{
   PropertyMap[Tag] = Xml->GetProperty(Tag);
}

void LoadMultiLineProperty(HXmlTree *Xml, const char *Tag, map<HString, vector<HString> > &MultiLinePropertyMap)
{
   vector<HString> StrVec;
   Xml->GetValues(Tag, StrVec);
   MultiLinePropertyMap[Tag] = StrVec;
}

void PrintDiff(bool &DiffFlag, const HString &Title, const HString &Tag, const HString &OldValue, const HString &NewValue)
{
   if ( !DiffFlag )
   {
      printf("%s\n", Title.c_str());
      DiffFlag = true;
   }

   printf("   %s : %s -> %s\n", Tag.c_str(), OldValue.c_str(), NewValue.c_str());
}

void PrintMultiLineDiff(bool &DiffFlag, const HString &Title, const HString &Tag, const vector<HString> &OldVec, const vector<HString> &NewVec)
{
   if ( !DiffFlag )
   {
      printf("%s\n", Title.c_str());
      DiffFlag = true;
   }

   printf("   %s :\n", Tag.c_str());

   for ( int i = 0; i < OldVec.size(); i++ )
   {
      printf("      %s\n", OldVec[i].c_str());
   }

   printf("      ---\n");

   for ( int i = 0; i < NewVec.size(); i++ )
   {
      printf("      %s\n", NewVec[i].c_str());
   }
}

void ComparePropertyMap(bool &DiffFlag, const HString &Title, map<HString, HString> &OldMap, map<HString, HString> &NewMap)
{
   map<HString, HString>::iterator OldIter;
   map<HString, HString>::iterator NewIter;

   for ( OldIter = OldMap.begin(), NewIter = NewMap.begin(); ; )
   {
      if ( OldIter == OldMap.end() && NewIter == NewMap.end() )
      {
         break;
      }
      if ( OldIter != OldMap.end() && NewIter == NewMap.end() )
      {
         PrintDiff(DiffFlag, Title, OldIter->first, OldIter->second, "");
         OldIter++;
         continue;
      }
      if ( OldIter == OldMap.end() && NewIter != NewMap.end() )
      {
         PrintDiff(DiffFlag, Title, NewIter->first, "", NewIter->second);
         NewIter++;
         continue;
      }
      if ( OldIter->first < NewIter->first )
      {
         PrintDiff(DiffFlag, Title, OldIter->first, OldIter->second, "");
         OldIter++;
         continue;
      }
      if ( OldIter->first > NewIter->first )
      {
         PrintDiff(DiffFlag, Title, NewIter->first, "", NewIter->second);
         NewIter++;
         continue;
      }
      if ( OldIter->second != NewIter->second )
      {
         PrintDiff(DiffFlag, Title, OldIter->first, OldIter->second, NewIter->second);
      }
      OldIter++;
      NewIter++;
   }
}

void CompareMultiLinePropertyMap(bool &DiffFlag, const HString &Title, map<HString, vector<HString> > &OldMap, map<HString, vector<HString> > &NewMap)
{
   map<HString, vector<HString> >::iterator OldIter;
   map<HString, vector<HString> >::iterator NewIter;

   for ( OldIter = OldMap.begin(), NewIter = NewMap.begin(); ; )
   {
      if ( OldIter == OldMap.end() && NewIter == NewMap.end() )
      {
         break;
      }
      if ( OldIter != OldMap.end() && NewIter == NewMap.end() )
      {
         PrintMultiLineDiff(DiffFlag, Title, OldIter->first, OldIter->second, vector<HString>());
         OldIter++;
         continue;
      }
      if ( OldIter == OldMap.end() && NewIter != NewMap.end() )
      {
         PrintMultiLineDiff(DiffFlag, Title, NewIter->first, vector<HString>(), NewIter->second);
         NewIter++;
         continue;
      }
      if ( OldIter->first < NewIter->first )
      {
         PrintMultiLineDiff(DiffFlag, Title, OldIter->first, OldIter->second, vector<HString>());
         OldIter++;
         continue;
      }
      if ( OldIter->first > NewIter->first )
      {
         PrintMultiLineDiff(DiffFlag, Title, NewIter->first, vector<HString>(), NewIter->second);
         NewIter++;
         continue;
      }
      if ( OldIter->second != NewIter->second )
      {
         PrintMultiLineDiff(DiffFlag, Title, OldIter->first, OldIter->second, NewIter->second);
      }
      OldIter++;
      NewIter++;
   }
}

bool LoadSbt(HSbtInfo &Sbt, const char *SbtFileName)
{
   HXmlTree SbtXml;

   if ( !SbtXml.LoadFromFile(SbtFileName) )
   {
      BkPrintError("Load %s fail, %s", SbtFileName, SbtXml.GetErrorString().c_str());
      return false;
   }

   {
      char TmpStr[500 + 1];
      char FileName[500 + 1];
      vector<HString> IncludeFiles;
      SbtXml.GetValues("INCLUDE_FILES", IncludeFiles);
      for ( int i = 0; i < IncludeFiles.size(); i++ )
      {
         snprintf(TmpStr, 500, "%s", IncludeFiles[i].c_str());
         BkRightTrim(TmpStr);
         BkToLower(TmpStr);
         snprintf(FileName, 500, "%s/bats/%s.sbt", SimbatBase, TmpStr);
         HXmlTree Xml(&SbtXml);
         if ( !Xml.LoadFromFile(FileName) )
         {
            BkPrintError("Load %s fail, %s", FileName, Xml.GetErrorString().c_str());
            return false;
         }
         vector<HXmlTree*> Groups;
         Xml.GetChildren("GROUP", Groups);
         for ( int j = 0; j < Groups.size(); j++ )
         {
            Xml.MoveChildTo(Groups[j], &SbtXml);
         }
      }
   }

   LoadProperty(&SbtXml, "ACROSS_DAY_END", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "AUTHOR", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "AUTO_SKIP", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "AUTO_RERUN", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "BANNER", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "BANNER_FONT_NAME", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "BANNER_FONT_SIZE", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "BANNER_FONT_STYLE", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "BANNER_POSITION", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "BATCH_TRIGGER", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "CHECK", Sbt.PropertyMap);
   LoadMultiLineProperty(&SbtXml, "DESCRIPTION", Sbt.MultiLinePropertyMap);
   LoadProperty(&SbtXml, "FADING_PERIOD", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "PARTITION_DITHER", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "JOB_TRIGGER", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "SECTION_LIST", Sbt.PropertyMap);
   LoadProperty(&SbtXml, "FLOW_CONTROL", Sbt.PropertyMap);

   vector<HXmlTree*> Groups;

   SbtXml.GetChildren("GROUP", Groups);

   for ( int i = 0; i < Groups.size(); i++ )
   {
      HGroupInfo Group;
      HXmlTree *GroupXml = Groups[i];
      HString GroupName = GroupXml->GetProperty("NAME");
      vector<HXmlTree*> Jobs;
      GroupXml->GetChildren("JOB", Jobs);
      for ( int j = 0; j < Jobs.size(); j++ )
      {
         HXmlTree *JobXml = Jobs[j];
         HString JobName = JobXml->GetProperty("NAME");
         HJobInfo Job;
         LoadProperty(JobXml, "TYPE", Job.PropertyMap);
         LoadProperty(JobXml, "X", Job.PropertyMap);
         LoadProperty(JobXml, "Y", Job.PropertyMap);
         LoadProperty(JobXml, "ADDITIONAL_LOG", Job.PropertyMap);
         LoadProperty(JobXml, "ALARM_TIME", Job.PropertyMap);
         LoadProperty(JobXml, "ALARM_TIME_TYPE", Job.PropertyMap);
         LoadProperty(JobXml, "ANCHOR", Job.PropertyMap);
         LoadProperty(JobXml, "AUTO_RERUN", Job.PropertyMap);
         LoadProperty(JobXml, "BUSINESS_TYPE", Job.PropertyMap);
         LoadProperty(JobXml, "COMMAND", Job.PropertyMap);
         LoadProperty(JobXml, "COND_NOT", Job.PropertyMap);
         LoadProperty(JobXml, "CONDITION_FILE", Job.PropertyMap);
         LoadMultiLineProperty(JobXml, "DESCRIPTION", Job.MultiLinePropertyMap);
         LoadProperty(JobXml, "ENLARGE", Job.PropertyMap);
         LoadProperty(JobXml, "EXCLUSIVE", Job.PropertyMap);
         LoadMultiLineProperty(JobXml, "EXTERN_JOB_LIST", Job.MultiLinePropertyMap);
         LoadProperty(JobXml, "LOOP_CONDITION_VARIABLE", Job.PropertyMap);
         LoadProperty(JobXml, "LOOP_END_TIME", Job.PropertyMap);
         LoadProperty(JobXml, "LOOP_INTERVAL", Job.PropertyMap);
         LoadProperty(JobXml, "LOOP_START_TIME", Job.PropertyMap);
         LoadProperty(JobXml, "JOB_TRIGGER", Job.PropertyMap);
         LoadProperty(JobXml, "PARALLEL_JOB_NUMBER", Job.PropertyMap);
         LoadProperty(JobXml, "PARTITION_FILE", Job.PropertyMap);
         LoadProperty(JobXml, "PRIORITY", Job.PropertyMap);
         LoadProperty(JobXml, "RESTORE_COMMAND", Job.PropertyMap);
         LoadMultiLineProperty(JobXml, "RESOURCE_LIST", Job.MultiLinePropertyMap);
         LoadProperty(JobXml, "RUNNING_GROUP", Job.PropertyMap);
         LoadProperty(JobXml, "SPECIAL_DATE", Job.PropertyMap);
         LoadProperty(JobXml, "TAG", Job.PropertyMap);

         LoadProperty(JobXml, "HIDE_ARROW", Job.PropertyMap);

         LoadProperty(JobXml, "EXTERN_GROUP", Job.PropertyMap);
         LoadProperty(JobXml, "EXTERN_JOB", Job.PropertyMap);

         LoadProperty(JobXml, "HOST_NAME", Job.PropertyMap);
         LoadProperty(JobXml, "PORT", Job.PropertyMap);
         LoadProperty(JobXml, "EVENT", Job.PropertyMap);

         LoadProperty(JobXml, "WAIT_FOR_COMMAND", Job.PropertyMap);
         LoadProperty(JobXml, "WAIT_FOR_FILE", Job.PropertyMap);
         LoadProperty(JobXml, "WAIT_FOR_OPERATOR", Job.PropertyMap);
         LoadProperty(JobXml, "WAIT_FOR_TIME", Job.PropertyMap);

         LoadProperty(JobXml, "GROUP", Job.PropertyMap);

         LoadProperty(JobXml, "TIME", Job.PropertyMap);

         LoadProperty(JobXml, "CAPTION", Job.PropertyMap);
         LoadProperty(JobXml, "BATCH", Job.PropertyMap);
         Group.JobMap[JobName] = Job;
      }
      vector<HXmlTree*> Links;
      GroupXml->GetChildren("LINK", Links);
      for ( int j = 0; j < Links.size(); j++ )
      {
         HXmlTree *LinkXml = Links[j];
         char TmpStr[500 + 1];
         HString StartJob = LinkXml->GetProperty("START_JOB");
         HString EndJob = LinkXml->GetProperty("END_JOB");
         snprintf(TmpStr, 500, "%s,%s", StartJob.c_str(), EndJob.c_str());
         Group.LinkSet.insert(TmpStr);
      }
      Sbt.GroupMap[GroupName] = Group;
   }

   return true;
}

bool CompareJob(const HString &JobName, HJobInfo &OldJob, HJobInfo &NewJob)
{
   bool DiffFlag = false;

   ComparePropertyMap(DiffFlag, HString("Property of ") + JobName + " :", OldJob.PropertyMap, NewJob.PropertyMap);
   CompareMultiLinePropertyMap(DiffFlag, HString("Property of ") + JobName + " :", OldJob.MultiLinePropertyMap, NewJob.MultiLinePropertyMap);
   return DiffFlag;
}

bool CompareGroup(const HString &GroupName, HGroupInfo &OldGroup, HGroupInfo &NewGroup)
{
   bool DiffFlag = false;
   set<HString> RemovedJobSet;

   map<HString, HJobInfo>::iterator OldIter;
   map<HString, HJobInfo>::iterator NewIter;

   for ( OldIter = OldGroup.JobMap.begin(), NewIter = NewGroup.JobMap.begin(); ; )
   {
      if ( OldIter == OldGroup.JobMap.end() && NewIter == NewGroup.JobMap.end() )
      {
         break;
      }
      if ( OldIter != OldGroup.JobMap.end() && NewIter == NewGroup.JobMap.end() )
      {
         printf("Removed job %s:%s\n", GroupName.c_str(), OldIter->first.c_str());
         RemovedJobSet.insert(OldIter->first);
         DiffFlag = true;
         OldIter++;
         continue;
      }
      if ( OldIter == OldGroup.JobMap.end() && NewIter != NewGroup.JobMap.end() )
      {
         printf("Added job %s:%s\n", GroupName.c_str(), NewIter->first.c_str());
         DiffFlag = true;
         NewIter++;
         continue;
      }
      if ( OldIter->first < NewIter->first )
      {
         printf("Removed job %s:%s\n", GroupName.c_str(), OldIter->first.c_str());
         RemovedJobSet.insert(OldIter->first);
         DiffFlag = true;
         OldIter++;
         continue;
      }
      if ( OldIter->first > NewIter->first )
      {
         printf("Added job %s:%s\n", GroupName.c_str(), NewIter->first.c_str());
         DiffFlag = true;
         NewIter++;
         continue;
      }
      if ( CompareJob(GroupName + JobNameDelimiter + OldIter->first, OldIter->second, NewIter->second) )
      {
         DiffFlag = true;
      }
      OldIter++;
      NewIter++;
   }

   set<HString>::iterator OldLinkIter;
   set<HString>::iterator NewLinkIter;

   for ( OldLinkIter = OldGroup.LinkSet.begin(), NewLinkIter = NewGroup.LinkSet.begin(); ; )
   {
      if ( OldLinkIter == OldGroup.LinkSet.end() && NewLinkIter == NewGroup.LinkSet.end() )
      {
         break;
      }
      if ( OldLinkIter != OldGroup.LinkSet.end() && NewLinkIter == NewGroup.LinkSet.end() )
      {
         vector<HString> StrVec;
         SplitString(*OldLinkIter, ',', StrVec, true);
         if ( RemovedJobSet.find(StrVec[0]) == RemovedJobSet.end() &&
              RemovedJobSet.find(StrVec[1]) == RemovedJobSet.end() )
         {
            printf("Removed link %s:%s\n", GroupName.c_str(), OldLinkIter->c_str());
            DiffFlag = true;
         }
         OldLinkIter++;
         continue;
      }
      if ( OldLinkIter == OldGroup.LinkSet.end() && NewLinkIter != NewGroup.LinkSet.end() )
      {
         printf("Added link %s:%s\n", GroupName.c_str(), NewLinkIter->c_str());
         DiffFlag = true;
         NewLinkIter++;
         continue;
      }
      if ( *OldLinkIter < *NewLinkIter )
      {
         vector<HString> StrVec;
         SplitString(*OldLinkIter, ',', StrVec, true);
         if ( RemovedJobSet.find(StrVec[0]) == RemovedJobSet.end() &&
              RemovedJobSet.find(StrVec[1]) == RemovedJobSet.end() )
         {
            printf("Removed link %s:%s\n", GroupName.c_str(), OldLinkIter->c_str());
            DiffFlag = true;
         }
         OldLinkIter++;
         continue;
      }
      if ( *OldLinkIter > *NewLinkIter )
      {
         printf("Added link %s:%s\n", GroupName.c_str(), NewLinkIter->c_str());
         DiffFlag = true;
         NewLinkIter++;
         continue;
      }
      OldLinkIter++;
      NewLinkIter++;
   }

   return DiffFlag;
}

bool Compare()
{
   bool DiffFlag = false;

   ComparePropertyMap(DiffFlag, "Property of BATCH :", OldSbt.PropertyMap, NewSbt.PropertyMap);
   CompareMultiLinePropertyMap(DiffFlag, "Property of BATCH :", OldSbt.MultiLinePropertyMap, NewSbt.MultiLinePropertyMap);

   map<HString, HGroupInfo>::iterator OldIter;
   map<HString, HGroupInfo>::iterator NewIter;

   for ( OldIter = OldSbt.GroupMap.begin(), NewIter = NewSbt.GroupMap.begin(); ; )
   {
      if ( OldIter == OldSbt.GroupMap.end() && NewIter == NewSbt.GroupMap.end() )
      {
         break;
      }
      if ( OldIter != OldSbt.GroupMap.end() && NewIter == NewSbt.GroupMap.end() )
      {
         printf("Removed group %s\n", OldIter->first.c_str());
         DiffFlag = true;
         OldIter++;
         continue;
      }
      if ( OldIter == OldSbt.GroupMap.end() && NewIter != NewSbt.GroupMap.end() )
      {
         printf("Added group %s\n", NewIter->first.c_str());
         DiffFlag = true;
         NewIter++;
         continue;
      }
      if ( OldIter->first < NewIter->first )
      {
         printf("Removed group %s\n", OldIter->first.c_str());
         DiffFlag = true;
         OldIter++;
         continue;
      }
      if ( OldIter->first > NewIter->first )
      {
         printf("Added group %s\n", NewIter->first.c_str());
         DiffFlag = true;
         NewIter++;
         continue;
      }
      if ( CompareGroup(OldIter->first, OldIter->second, NewIter->second) )
      {
         DiffFlag = true;
      }
      OldIter++;
      NewIter++;
   }

   return DiffFlag;
}

void PrintUsage()
{
   fprintf(stderr, "Usage : [PATH/]simbat_diff sbt_file_old sbt_file_new\n");
}

bool Init()
{
   SimbatBase = getenv("SIMBAT_BASE");

   if ( SimbatBase == NULL )
   {
      BkPrintError("$SIMBAT_BASE not defined");
      return false;
   }

   HString ErrStr;

   if ( !LoadCfg(ErrStr) )
   {
      BkPrintError("Load config.xml fail, %s", ErrStr.c_str());
      PrintUsage();
      return 8;
   }

   return true;
}

int main(int argc, char *argv[])
{
   if ( argc != 3 )
   {
      PrintUsage();
      return 8;
   }

   if ( strchr(argv[0], '/') != NULL )
   {
      SetBaseByPath(argv[0]);
   }

   if ( !Init() )
   {
      return 8;
   }

   if ( !LoadSbt(OldSbt, argv[1]) )
   {
      return 8;
   }

   if ( !LoadSbt(NewSbt, argv[2]) )
   {
      return 8;
   }

   if ( Compare() )
   {
      return 8;
   }

   return 0;
}
